#include "header.hpp"
using namespace std;

//producer and cosumer model
class TaskQueue{
public:
    TaskQueue(int capa)
    : _capacity(capa)
    , _que()
    , _mutex()
    , _notFull()
    , _notEmpty()
    {}

    bool empty() const{
        return _que.size() == 0;
    }

    bool full() const{
        return _capacity == _que.size();
    }

    //produce data
    void push(const int& value){
        //(1)lock
        unique_lock<mutex> ul(_mutex);

        //(2)tell if queue is full
        while(full()){
            //(2.1)full -> producer sleep
            _notFull.wait(ul);
        }
        //(2.2)not full -> push data into queue and notify consumer
        _que.push(value);
        _notEmpty.notify_one();

        //(3)unlock: automatically
    }

    //consume data
    int pop(){
        //(1)lock
        unique_lock<mutex> ul(_mutex);

        //(2)tell if queue is empty
        while(empty()){
            //(2.1)empty -> producer sleep
            _notEmpty.wait(ul);
        }
        //(2.2)not empty -> push data into queue and notify consumer
        int tmp = _que.front();
        _que.pop();
        _notFull.notify_one();

        //(3)unlock: automatically

        return tmp;
    }

private:
    size_t _capacity;
    queue<int> _que;
    mutex _mutex;
    condition_variable _notFull;
    condition_variable _notEmpty;
};
//end of class TaskQueue

class Producer{
public:
    void produce(TaskQueue& tq){
        srand(time(NULL));

        int count = 20;
        while(count--){
            int num = rand() % 100;
            tq.push(num);
            cout << ">>Producer produces num = " << num << endl;
            this_thread::sleep_for(chrono::seconds(1));
        }
    }
};

class Consumer{
public:
    void consume(TaskQueue& tq){
        int count = 20;
        while(count--){
            int num = tq.pop();
            cout << ">>Consumer consumes = " << num << endl;
            this_thread::sleep_for(chrono::seconds(1));
        }
    }
};


void test16(){
    Producer producer;
    Consumer consumer;
    TaskQueue taskQue(10);

    thread pro(&Producer::produce, &producer, ref(taskQue));
    thread con(&Consumer::consume, &consumer, ref(taskQue));

    pro.join();
    con.join();
}
